package cn.yx.common.jpa.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.yx.common.core.enums.OrderByTypeEnum;
import cn.yx.common.core.pojo.singletable.SingleCond;
import cn.yx.common.core.util.StringUtil;
import cn.yx.common.core.util.reflect.ReflectUtil;
import cn.yx.common.jpa.pojo.SingleDo;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;

import javax.persistence.criteria.*;
import java.lang.reflect.Field;
import java.util.*;

/**
 * <p>JPA Specification服务类，用于获取Specification对象</p>
 *
 * @param <Do>   DO类
 * @param <Cond> Cond类
 * @author Wgssmart
 */
@Component
public class JpaSpecificationService<Do extends SingleDo, Cond extends SingleCond> {

    private static final String SERIAL_VERSION_UID = "serialVersionUID";
    private static final String PROPERTY_ID = "id";
    private static final String PROPERTY_VERSION = "version";
    private static final String PROPERTY_IS_ACTIVE = "isActive";
    private static final String PROPERTY_CREATE_BY = "createBy";
    private static final String PROPERTY_CREATE_TIME = "createTime";
    private static final String PROPERTY_UPDATE_BY = "updateBy";
    private static final String PROPERTY_UPDATE_TIME = "updateTime";

    private static final String PROPERTY_SORT_FIELDS = "sortFields";
    private static final String PROPERTY_LIKE_FIELDS = "likeFields";
    private static final String PROPERTY_MAX = "Max";
    private static final String PROPERTY_MIN = "Min";
    private static final String PROPERTY_IN = "In";

    /**
     * 查询条件中不需要设置到where部分的字段
     */
    private static final List<String> NO_QUERY_WHERE_COLUMN_LIST = List.of(SERIAL_VERSION_UID, PROPERTY_SORT_FIELDS);

    /**
     * 构建jpa specification查询条件
     *
     * @param selectCond Cond查询对象
     * @return
     */
    public Specification<Do> buildCondSpecification(Cond selectCond) {
        return (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            Field[] queryFields = selectCond.getClass().getDeclaredFields();
            for (Field field : queryFields) {
                String fieldName = field.getName();
                if (NO_QUERY_WHERE_COLUMN_LIST.contains(fieldName)) {
                    continue;
                }
                if (!PROPERTY_LIKE_FIELDS.equals(fieldName)) {
                    buildCondSpecificationWhere(predicates, root, criteriaBuilder, selectCond, fieldName);
                } else {
                    buildCondSpecificationWhereLike(predicates, root, criteriaBuilder, selectCond);
                }
            }
            buildCondSpecificationSortColumns(root, query, criteriaBuilder, selectCond);
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        };
    }

    /**
     * 构建jpa specification中的where条件部分
     *
     * @param predicates      javax.persistence.criteria.Predicate
     * @param root            javax.persistence.criteria.Root
     * @param criteriaBuilder javax.persistence.criteria.CriteriaBuilder
     * @param selectCond      Cond查询对象
     * @param fieldName       属性名
     */
    private void buildCondSpecificationWhere(List<Predicate> predicates, Root<Do> root,
                                             CriteriaBuilder criteriaBuilder, Cond selectCond, String fieldName) {
        Object fieldValue = ReflectUtil.invokeGetter(selectCond, fieldName);
        if (Objects.nonNull(fieldValue)) {
            if (fieldName.endsWith(PROPERTY_MAX)) {
                predicates.add(lessThanOrEqualTo(root, criteriaBuilder, fieldName, fieldValue));
            } else if (fieldName.endsWith(PROPERTY_MIN)) {
                predicates.add(greaterThanOrEqualTo(root, criteriaBuilder, fieldName, fieldValue));
            } else if (fieldName.endsWith(PROPERTY_IN)) {
                CriteriaBuilder.In<Object> in = criteriaBuilder.in(root.get(fieldName));
                in.value(fieldValue);
                predicates.add(in);
            } else {
                predicates.add(criteriaBuilder.equal(root.get(fieldName), fieldValue));
            }
        }
    }

    /**
     * 构建jpa specification中的where like部分
     *
     * @param predicates      javax.persistence.criteria.Predicate
     * @param root            javax.persistence.criteria.Root
     * @param criteriaBuilder javax.persistence.criteria.CriteriaBuilder
     * @param selectCond      Cond查询对象
     */
    @SuppressWarnings("unchecked")
    private void buildCondSpecificationWhereLike(List<Predicate> predicates, Root<Do> root,
                                                 CriteriaBuilder criteriaBuilder, Cond selectCond) {
        List<String> likeFieldNames = ReflectUtil.invokeGetter(selectCond, PROPERTY_LIKE_FIELDS, List.class);
        if (CollectionUtil.isNotEmpty(likeFieldNames)) {
            for (String likeFieldName : likeFieldNames) {
                Object fieldValue = ReflectUtil.invokeGetter(selectCond, likeFieldName);
                if (Objects.nonNull(fieldValue)) {
                    predicates.add(criteriaBuilder.like(root.get(likeFieldName), fieldValue.toString()));
                }
            }
        }
    }

    /**
     * 构建jpa specification中的order by部分
     *
     * @param root            javax.persistence.criteria.Root
     * @param query           javax.persistence.criteria.CriteriaCond
     * @param criteriaBuilder javax.persistence.criteria.CriteriaBuilder
     * @param selectCond      Cond查询对象
     */
    @SuppressWarnings("unchecked")
    private void buildCondSpecificationSortColumns(Root<Do> root, CriteriaQuery<?> query,
                                                   CriteriaBuilder criteriaBuilder, Cond selectCond) {
        Map<String, String> sortFields = ReflectUtil.invokeGetter(selectCond, PROPERTY_SORT_FIELDS, Map.class);
        if (CollectionUtil.isNotEmpty(sortFields)) {
            List<Order> orderList = new ArrayList<>();
            sortFields.forEach((field, orderType) -> {
                if (OrderByTypeEnum.ASC.getValue().equalsIgnoreCase(orderType)) {
                    orderList.add(criteriaBuilder.asc(root.get(field)));
                } else if (OrderByTypeEnum.DESC.getValue().equalsIgnoreCase(orderType)) {
                    orderList.add(criteriaBuilder.desc(root.get(field)));
                } else {
                    orderList.add(criteriaBuilder.asc(root.get(field)));
                }
            });
            query.orderBy(orderList);
        }
    }

    /**
     * 构建jpa specification <= 条件
     *
     * @param root            javax.persistence.criteria.Root
     * @param criteriaBuilder javax.persistence.criteria.CriteriaBuilder
     * @param fieldName       属性名
     * @param fieldValue      属性值
     * @return
     */
    private Predicate lessThanOrEqualTo(Root<Do> root, CriteriaBuilder criteriaBuilder,
                                        String fieldName, Object fieldValue) {
        Class<?> clazz = fieldValue.getClass();
        if (clazz == Byte.class) {
            Byte fieldVal = (Byte) fieldValue;
            return criteriaBuilder.lessThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MAX, StringUtil.EMPTY_STRING)).as(Byte.class), fieldVal);
        }
        if (clazz == Short.class) {
            Short fieldVal = (Short) fieldValue;
            return criteriaBuilder.lessThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MAX, StringUtil.EMPTY_STRING)).as(Short.class), fieldVal);
        }
        if (clazz == Integer.class) {
            Integer fieldVal = (Integer) fieldValue;
            return criteriaBuilder.lessThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MAX, StringUtil.EMPTY_STRING)).as(Integer.class), fieldVal);
        }
        if (clazz == Long.class) {
            Long fieldVal = (Long) fieldValue;
            return criteriaBuilder.lessThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MAX, StringUtil.EMPTY_STRING)).as(Long.class), fieldVal);
        }
        if (clazz == Float.class) {
            Float fieldVal = (Float) fieldValue;
            return criteriaBuilder.lessThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MAX, StringUtil.EMPTY_STRING)).as(Float.class), fieldVal);
        }
        if (clazz == Double.class) {
            Double fieldVal = (Double) fieldValue;
            return criteriaBuilder.lessThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MAX, StringUtil.EMPTY_STRING)).as(Double.class), fieldVal);
        }
        if (clazz == Date.class) {
            Date fieldVal = (Date) fieldValue;
            return criteriaBuilder.lessThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MAX, StringUtil.EMPTY_STRING)).as(Date.class), fieldVal);
        }
        String fieldVal = (String) fieldValue;
        return criteriaBuilder.lessThanOrEqualTo(
                root.get(fieldName.replace(PROPERTY_MAX, StringUtil.EMPTY_STRING)).as(String.class), fieldVal);
    }

    /**
     * 构建jpa specification >= 条件
     *
     * @param root            javax.persistence.criteria.Root
     * @param criteriaBuilder javax.persistence.criteria.CriteriaBuilder
     * @param fieldName       属性名
     * @param fieldValue      属性值
     * @return
     */
    private Predicate greaterThanOrEqualTo(Root<Do> root, CriteriaBuilder criteriaBuilder,
                                           String fieldName, Object fieldValue) {
        Class<?> clazz = fieldValue.getClass();
        if (clazz == Byte.class) {
            Byte fieldVal = (Byte) fieldValue;
            return criteriaBuilder.greaterThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MIN, StringUtil.EMPTY_STRING)).as(Byte.class), fieldVal);
        }
        if (clazz == Short.class) {
            Short fieldVal = (Short) fieldValue;
            return criteriaBuilder.greaterThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MIN, StringUtil.EMPTY_STRING)).as(Short.class), fieldVal);
        }
        if (clazz == Integer.class) {
            Integer fieldVal = (Integer) fieldValue;
            return criteriaBuilder.greaterThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MIN, StringUtil.EMPTY_STRING)).as(Integer.class), fieldVal);
        }
        if (clazz == Long.class) {
            Long fieldVal = (Long) fieldValue;
            return criteriaBuilder.greaterThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MIN, StringUtil.EMPTY_STRING)).as(Long.class), fieldVal);
        }
        if (clazz == Float.class) {
            Float fieldVal = (Float) fieldValue;
            return criteriaBuilder.greaterThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MIN, StringUtil.EMPTY_STRING)).as(Float.class), fieldVal);
        }
        if (clazz == Double.class) {
            Double fieldVal = (Double) fieldValue;
            return criteriaBuilder.greaterThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MIN, StringUtil.EMPTY_STRING)).as(Double.class), fieldVal);
        }
        if (clazz == Date.class) {
            Date fieldVal = (Date) fieldValue;
            return criteriaBuilder.greaterThanOrEqualTo(
                    root.get(fieldName.replace(PROPERTY_MIN, StringUtil.EMPTY_STRING)).as(Date.class), fieldVal);
        }
        String fieldVal = (String) fieldValue;
        return criteriaBuilder.greaterThanOrEqualTo(
                root.get(fieldName.replace(PROPERTY_MIN, StringUtil.EMPTY_STRING)).as(String.class), fieldVal);
    }

}
